package com.uid.util;

import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 * Snowflake ID生成器
 * <p>
 * Snowflake 生成的 unique ID 的组成 (由高位到低位):
 * 41 bits: Timestamp (毫秒级，够用69.7年)
 * 10 bits: 分组ID，根据需要填充数据，如(datacenter ID 5 bits + worker ID 5 bits)
 * 12 bits: sequence number
 * 一共 63 bits (最高位是 0)
 *
 * 理论上：
 * 每个毫秒，每个分组，最大可生成4095个ID。
 * 即：每个分组，1秒可以生成4095000个ID
 * <p>
 * unique ID 生成过程:
 * 10 bits 的机器号，或者分组ID (使用时需要保证不会出现重复)
 * 41 bits 的 Timestamp: 每次要生成一个新 ID 的时候, 都会获取一下当前的 Timestamp, 然后分两种情况生成 sequence number:
 * 如果当前的 Timestamp 和前一个已生成 ID 的 Timestamp 相同 (在同一毫秒中), 就用前一个 ID 的 sequence number + 1 作为新的 sequence number (12 bits); 如果本毫秒内的所有 ID 用完, 等到下一毫秒继续 (这个等待过程中, 不能分配出新的 ID)
 * 如果当前的 Timestamp 比前一个 ID 的 Timestamp 大, 随机生成一个初始 sequence number (12 bits) 作为本毫秒内的第一个 sequence number
 * <p>
 *
 * @author Arnold Yang
 * @since 2018-11-16
 */
public class IdGenerator {

  // 用于控制nextId方法的并发访问
  private final ReentrantLock lock = new ReentrantLock();

  // 上一个毫秒数
  private long lastTimestamp = 0;

  // 分组ID
  private int groupId = 0;
  public static final int GROUP_ID_BITS = 10;

  // 移位位数
  public static final int SEQUENCE_BITS = 12;
  public static final int GROUP_ID_SHIFT_BITS = SEQUENCE_BITS;
  public static final int TIMESTAMP_SHIFT_BITS = SEQUENCE_BITS + GROUP_ID_BITS;

  // 自增序列初始值
  private int sequence = 0;

  // 自增序列初掩码 111111111111
  private final int sequenceMask = -1 ^ (-1 << SEQUENCE_BITS);

  /**
   * 生成ID，先尝试获取锁
   * 成功：生成id并返回
   * 失败：抛出异常，防止并发情况下导致线程都阻塞住
   *
   * @return
   */
  public long nextId() throws Exception{

    if (lock.tryLock()) {
      try {

        long timestamp = currentMillis();
        if (timestamp < lastTimestamp) {
          throw new RuntimeException("系统时间不正确");
        }
        if (lastTimestamp == timestamp) {
          sequence = (sequence + 1) & sequenceMask;
          if (sequence == 0) {
            timestamp = tilNextMillis(lastTimestamp);
          }
        } else {
          sequence = 0;
        }
        lastTimestamp = timestamp;

        return (timestamp << TIMESTAMP_SHIFT_BITS) | (groupId << GROUP_ID_SHIFT_BITS) | sequence;

      } catch (Exception e) {
        // Don't worries.
      } finally {
        lock.unlock();
      }
    }

    throw new IllegalStateException("请重试");
  }

  /**
   * @param lastTimestamp
   * @return
   */
  private long tilNextMillis(long lastTimestamp) {
    long timestamp = currentMillis();
    while (timestamp <= lastTimestamp) {
      timestamp = currentMillis();
    }

    return timestamp;
  }

  /**
   * 系统当前毫秒数
   *
   * @return
   */
  private long currentMillis() {
    return System.currentTimeMillis();
  }

  public void setGroupId(int groupId) {
    this.groupId = groupId;
  }
}
